#pragma once
#include "TypeDefinitions.h"
#include "RandomGenerators.h"
#include "TriangleBuilder.h"
#include "ImageComparator.h"
#include "CPolygon.h"
#include <algorithm>

class Mutator
{
public:

	Mutator(TriangleBuilder& bu, Image* original, Image* generated, ImageComparator* comparator):
		builder(bu),
		original(original),
		generated(generated),
		comparator(comparator)
	{

		black[0] = 0;
		black[1] = 0;
		black[2] = 0;
	}

	bool isIntersection(CPolygon* first, CPolygon* second)
	{
		return (first->leftUpX <= second->rightBottomX && first->rightBottomX >= second->leftUpX &&
			first->leftUpY <= second->rightBottomY && first->rightBottomY >= second->leftUpY);
	}



	~Mutator(void)
	{
	}


	void verifyIntersections(Polygons& polygons)
	{
		for(int i = 0; i<polygons.size(); i++)
		{
			int oldLeftUpX = polygons[i]->leftUpX;
			int oldLeftUpY = polygons[i]->leftUpY;

			int oldRightBottomX = polygons[i]->rightBottomX;
			int oldRightBottomY = polygons[i]->rightBottomY;

			polygons[i]->calculateBox();

			if((polygons[i]->leftUpX != oldLeftUpX) ||  (polygons[i]->leftUpY != oldLeftUpY) || 
				(polygons[i]->rightBottomX != oldRightBottomX) || (polygons[i]->rightBottomY != oldRightBottomY) )
			{
				std::cout<<"ZLE WYLICZONY BOUND BOX"<<std::endl;
			}
		}
		for(int j = 0; j < polygons.size(); j++)   // czyich sasiadow badamy j
		{
			if(polygons[j]->positionZ != j)
			{
				std::cout<<"ZLE USTAWIONY ZETKI"<<std::endl;
			}

			for( int z = 0; z < polygons.size(); z++)
			{
				if(polygons[j] != polygons[z])
				{
					if(isIntersection(polygons[j], polygons[z]))
					{
						if( findInNeighours(polygons[j]->neighbours, polygons[z]) == -1)
						{
							std::cout<<"ERROR, not found on neighbours"<<std::endl;
						}
					}
				}
			}
			int lastZ = -1;
			for(int i=0; i < polygons[j]->neighbours.size() ; i++)   //konkretny sasiad
			{
				if(polygons[j]->neighbours[i]->positionZ <= lastZ)
				{
					std::cout<<"ZLA KOLEJNOSC NA LISCIE SASIADOW"<<std::endl;
				}
				if( !isIntersection(polygons[j],  polygons[j]->neighbours[i]))
				{
					std::cout<<"NIE MA TAKIEGO PRZECIECIA"<<std::endl;
				}

				if (findInNeighours(polygons[j]->neighbours[i]->neighbours, polygons[j]) == -1)  //dodac sprawdzanie kolejnosci
				{    //dodac sprawdzenie: przejechac kazdy trojkat z kazdym i sprawdzic czy rzeczywiscie sie spotykaja badz nie

					//dodac sprawdzenie czy rzeczywiscie w polygons na pozycji  i  ustawiony jest polygon z "Z" = 1


					std::cout<<"FUCK!";
				}
			}
		}
	}

	bool mutate(Polygons& polygons)
	{
		//verifyIntersections(polygons);
		mutated = false;
		newBest = false;
		fitnessDiff = 0;

		if(isLucky(addNewPolygonFrequency))
		{
			addNewPolygon(polygons);
			if(mutated){return true;}
		}
		
		

		if(isLucky(removePolygonFrequency))
		{
			removePolygon(polygons);
			if(mutated){return true;}
		}		
	
		if(isLucky(movePolygonFrequency))
		{
			movePolygon(polygons);
			if(mutated){return true;}
		}
		

		
		for(Polygons::iterator it = polygons.begin(); it < polygons.end(); it++)
		{
			mutateColors(*it);
			if(mutated){ return true;}
			mutateVerticles(*it, polygons);
			if(mutated){return true;}
			addPoint(*it, polygons);
			if(mutated){return true;}
			removePoint(*it, polygons);	
			if(mutated){return true;}
		}
		
		return mutated;
	}
	
	void addPoint(CPolygon* poly, Polygons& polygons)
	{
		Verticles* verticles = poly->verticles;
		int newPointsNumber = verticles->width() + 1;
		if( newPointsNumber <= maxPointsPerPolygon)
		{
			if(isLucky(addNewPointFrequency))
			{		
				int positionAfter = random(1, verticles->width() - 1 );
				int firstX = (*verticles)(positionAfter, 0);
				int firstY = (*verticles)(positionAfter, 1);

				int secondX = (*verticles)(positionAfter - 1, 0);
				int secondY = (*verticles)(positionAfter - 1, 1);

				int newX = std::max(0, std::min(width, (firstX + secondX) /2 + random(-1*newPointDivergence, newPointDivergence)));
				int newY = std::max(0, std::min(height, (firstY + secondY) /2 + random(-1*newPointDivergence, newPointDivergence)));

				Verticles oldVerticles = *poly->verticles;       // TO DO podmiana tak zeby wiecej roboty bylo w negatywnym przypadku

				poly->calculateBox();

				int oldLeftUpX = poly->leftUpX;
				int oldLeftUpY = poly->leftUpY;

				int oldRightBottomX = poly->rightBottomX;
				int oldRightBottomY = poly->rightBottomY;

				int leftUpX = std::min(oldLeftUpX, newX);
				int leftUpY = std::min(oldLeftUpY, newY);

				int rightBottomX = std::max(oldRightBottomX, newX);
				int rightBottomY = std::max(oldRightBottomY, newY);


				poly->leftUpX = leftUpX;
				poly->leftUpY = leftUpY;

				poly->rightBottomX = rightBottomX;
				poly->rightBottomY = rightBottomY;

				Polygons neighbours;
				makeNeighbours(polygons, poly, neighbours);

				generated->draw_rectangle(leftUpX, leftUpY, rightBottomX, rightBottomY, black, 1);

				drawNeighboursAndMe(neighbours, poly, generated);

				double oldDiff = comparator->compareFragment(original, generated, leftUpX, leftUpY, rightBottomX, rightBottomY);

				verticles->resize(newPointsNumber, 2, 1, 1, 0);
				
				for(int i = newPointsNumber - 2; i >= positionAfter; i--)
				{
					(*verticles)(i + 1, 0) = (*verticles)(i, 0);
					(*verticles)(i + 1, 1) = (*verticles)(i, 1);
				}

				(*verticles)(positionAfter, 0) =  newX;
				(*verticles)(positionAfter, 1) = newY;

				generated->draw_rectangle(leftUpX, leftUpY, rightBottomX, rightBottomY, black, 1);

				drawNeighboursAndMe(neighbours, poly, generated);

				double newDiff = comparator->compareFragment(original, generated, leftUpX, leftUpY, rightBottomX, rightBottomY);

				fitnessDiff = newDiff - oldDiff;
				if(newDiff <= oldDiff)
				{
					newBest = true;
					poly->calculateBox();
					updateNeighboursAfterSizeChange(neighbours, poly);

				}else{
					*poly->verticles = oldVerticles;
					poly->calculateBox();
				}

				mutated = true;
				return;
			}
		}

	}


	void removePoint(CPolygon* poly, Polygons& polygons)
	{
		int newPointsNumber = poly->verticles->width() - 1;
		if( newPointsNumber > minPointsPerPolygon)												
		{
			if(isLucky(removePointFrequency))
			{		
				int positionToRemove = random(0, poly->verticles->width() - 1 );

				Verticles oldVerticles = *poly->verticles;

				generated->draw_rectangle(poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY, black, 1);

				drawNeighboursAndMe(poly->neighbours, poly, generated);

				double oldDiff = comparator->compareFragment(original, generated, poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY);

				for(int i = positionToRemove ; i < poly->verticles->width() - 1; i++)
				{
					(*poly->verticles)(i, 0) = (*poly->verticles)(i+1, 0);
					(*poly->verticles)(i, 1) = (*poly->verticles)(i+1, 1);
				}

				poly->verticles->resize(newPointsNumber, 2, 1, 1, 0);

				generated->draw_rectangle(poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY, black, 1);

				drawNeighboursAndMe(poly->neighbours, poly, generated);

				double newDiff = comparator->compareFragment(original, generated, poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY);

				fitnessDiff = newDiff - oldDiff;

				if(newDiff <= oldDiff)
				{
					newBest = true;
					poly->calculateBox();
					Polygons neighbours;
					makeNeighbours(polygons, poly, neighbours);
					updateNeighboursAfterSizeChange(neighbours, poly);
				}else
				{
					*poly->verticles = oldVerticles;
					poly->calculateBox();
				}

				mutated = true;
				return;
			}
		}
	}


	void makeNeighbours(Polygons& polygons, CPolygon* poly, Neighbours& neighbours)
	{
		for(int i=0; i < polygons.size(); i++)
		{
			if ( polygons[i] != poly)
			{
				if ( isIntersection(polygons[i], poly) )
				{
					neighbours.push_back(polygons[i]);
				}
			}
		}
	}

	void mutateVerticles(CPolygon* poly, Polygons& polygons)
	{
		Verticles* verticles = poly->verticles;
		for(int i = 0; i<verticles->width(); i++)					
		{
			if(isLucky(mutateVerticleFrequency))
			{
				int modifiedVerticle = i;

				poly->calculateBox();   //mozna sprawdzic tylko nowe wierzcholki zamiast liczyc wszystko na nowo

				int verticleOldX = (*verticles)(modifiedVerticle , 0);
				int verticleOldY = (*verticles)(modifiedVerticle , 1);

				int newX = std::max(std::min((*verticles)(modifiedVerticle , 0) + random(-1*movePointDivergence, movePointDivergence), width - 1), 0);  //kontrola koncow obszaru
				int newY = std::max(std::min((*verticles)(modifiedVerticle , 1) + random(-1*movePointDivergence, movePointDivergence), height -1 ), 0);


				int leftUpX = std::min(poly->leftUpX, newX);
				int leftUpY = std::min(poly->leftUpY, newY);
				int rightBottomX = std::max(poly->rightBottomX, newX);
				int rightBottomY = std::max(poly->rightBottomY, newY);
		

				poly->leftUpX = leftUpX;
				poly->leftUpY = leftUpY;
				poly->rightBottomX = rightBottomX;
				poly->rightBottomY = rightBottomY;

				Polygons neighbours;
				makeNeighbours(polygons, poly, neighbours);

				generated->draw_rectangle(leftUpX, leftUpY, rightBottomX, rightBottomY, black, 1);
				drawNeighboursAndMe(neighbours, poly, generated);
				double oldDiff = comparator->compareFragment(original, generated, leftUpX, leftUpY, rightBottomX, rightBottomY);

				(*verticles)(modifiedVerticle , 0) = newX;
				(*verticles)(modifiedVerticle , 1) = newY;

				//poly->calculateBox();

				generated->draw_rectangle(leftUpX, leftUpY, rightBottomX, rightBottomY, black, 1);
				drawNeighboursAndMe(neighbours, poly, generated);	
				double newDiff = comparator->compareFragment(original, generated, leftUpX, leftUpY, rightBottomX, rightBottomY);

				fitnessDiff = newDiff - oldDiff;

				if(newDiff < oldDiff)
				{
					poly->calculateBox();
					updateNeighboursAfterSizeChange(neighbours, poly);
					newBest = true;
				}else{
					(*verticles)(modifiedVerticle , 0) = verticleOldX;
					(*verticles)(modifiedVerticle , 1) = verticleOldY;
					poly->calculateBox();
				}
				mutated = true;
				return;
			}
		}
	}

	void updateNeighboursAfterSizeChange(Neighbours& neighbours, CPolygon* poly)
	{
		for(int x=0; x < neighbours.size(); x++)
		{
			if(!isIntersection(poly, neighbours[x]))
			{
				neighbours.erase(neighbours.begin() + x);
				x--;
			}else
			{
				int positionInOld = findInNeighours(poly->neighbours, neighbours[x]);
				if( positionInOld != -1)
				{
					poly->neighbours.erase( poly->neighbours.begin() + positionInOld);
				}else
				{ 
					insertIntoNeighbours(neighbours[x]->neighbours, poly);
				}
			}
		}

		for(int x = 0 ; x < poly->neighbours.size(); x++)
		{
			int minePosInOld = findInNeighours(poly->neighbours[x]->neighbours, poly);
			poly->neighbours[x]->neighbours.erase(poly->neighbours[x]->neighbours.begin() +minePosInOld);
		}

		poly->neighbours = neighbours;
	}

	int findInNeighours(Neighbours& neigbours, CPolygon* poly)
	{
		for(int i=0; i <neigbours.size(); i++)
		{
			if(neigbours[i]->positionZ == poly->positionZ)
			{
				return i;
			}
		}
		return -1;
	}
	
	
	void mutateColors(CPolygon* poly)   
	{
		for(int i=0; i<channelsNumber; i++)
		{
			if(isLucky(mutateColorFrequency))
			{
				int oldColor = poly->color[i];
				int oldColorIndex = i;
				
				int newColor = random(255);

				generated->draw_rectangle(poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY, black, 1);

				drawNeighboursAndMe(poly->neighbours, poly, generated);

				double oldDiff = comparator->compareFragment(original, generated, poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY);

				poly->color[i] = newColor;

				generated->draw_rectangle(poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY, black, 1);

				drawNeighboursAndMe(poly->neighbours, poly, generated);

				double newDiff = comparator->compareFragment(original, generated, poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY);
				
				fitnessDiff = newDiff - oldDiff;

				if(newDiff < oldDiff)
				{
					newBest = true;
				}else{
					poly->color[i] = oldColor;
				}
				mutated = true;
				return;
			}
		}
		
		if(isLucky(mutateOpacityFrequency))
		{
			float newOpacity = randomFloat(opacityMin, opacityMax);
			float oldOpacity = poly->opacity;

			generated->draw_rectangle(poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY, black, 1);

			drawNeighboursAndMe(poly->neighbours, poly, generated);

			double oldDiff = comparator->compareFragment(original, generated, poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY);

			poly->opacity  = newOpacity;

			generated->draw_rectangle(poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY, black, 1);

			drawNeighboursAndMe(poly->neighbours, poly, generated);

			double newDiff = comparator->compareFragment(original, generated, poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY);

			fitnessDiff = newDiff - oldDiff;

			if(newDiff < oldDiff)
			{
				newBest = true;
				
			}else{
				poly->opacity = oldOpacity;
			}
			mutated = true;
			return;
		}
	}

	void drawNeighboursAndMe(Neighbours& neighbours, CPolygon* poly, Image* generated)
	{
		if(poly != NULL)
		{
			int i = 0;
			for(; i < neighbours.size() && (neighbours[i]->positionZ < poly->positionZ); i++)
			{
				generated->draw_polygon( *neighbours[i]->verticles, neighbours[i]->color, neighbours[i]->opacity );
			}

			generated->draw_polygon(*poly->verticles, poly->color, poly->opacity);

			for(; i < neighbours.size(); i++)
			{
				//neighbours[i]->print();
				generated->draw_polygon( *neighbours[i]->verticles, neighbours[i]->color, neighbours[i]->opacity );	
			}
		}else
		{
			for(int i = 0; i < neighbours.size() ; i++)
			{
				generated->draw_polygon( *neighbours[i]->verticles, neighbours[i]->color, neighbours[i]->opacity );
			}
		}

	}

	void addNewPolygon(Polygons& polygons)
	{
		if(polygons.size() < maxPolygons)
		{			
			CPolygon * poly= builder.buildSmallTriangle();

			int zPosition = random(polygons.size()+1);

			poly->positionZ = zPosition;
				
			generated->draw_rectangle(poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY, black, 1);

			Polygons neighbours;
			makeNeighbours(polygons, poly, neighbours);

			drawNeighboursAndMe(neighbours, NULL, generated);


			double oldDiff = comparator->compareFragment(original, generated, poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY);
		

			generated->draw_rectangle(poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY, black, 1);

			
			drawNeighboursAndMe(neighbours, poly, generated);
			
			double newDiff = comparator->compareFragment(original, generated, poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY);

			fitnessDiff = newDiff - oldDiff;

			if(newDiff < oldDiff)
			{
				newBest = true;

				Polygons::iterator it = polygons.insert(polygons.begin() + zPosition, poly) ;
				it++;
				for( it; it != polygons.end(); it++)
				{
					(*it)->positionZ++;
				}

				poly->neighbours = neighbours;

				for(int i=0; i < neighbours.size(); i++)
				{
					insertIntoNeighbours(neighbours[i]->neighbours, poly);
				}
					
			}else{
				delete poly;
			}

			mutated = true;
			return;
		}
	}


	void insertIntoNeighbours(std::vector<CPolygon* >& neighbours, CPolygon* poly)
	{
		if(neighbours.size() == 0 )
		{
			neighbours.push_back(poly);
			return;
		}

		Neighbours::iterator it = neighbours.begin();

		for(; it != (neighbours.end() - 1); it++)
		{
			if ( poly->positionZ < (*it)->positionZ)
			{
				neighbours.insert(it, poly);
				return;
			}
		}

		neighbours.push_back(poly);
	}

	void removePolygon(Polygons& polygons)
	{
		if(polygons.size() > 1)
		{
			int positionToRemove = random(polygons.size());
			CPolygon* poly = polygons[positionToRemove];

			generated->draw_rectangle(poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY, black, 1);
			drawNeighboursAndMe(poly->neighbours, poly, generated);
			double oldDiff = comparator->compareFragment(original, generated, poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY);


			generated->draw_rectangle(poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY, black, 1);
			drawNeighboursAndMe(poly->neighbours, NULL, generated);
			double newDiff = comparator->compareFragment(original, generated, poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY);

			fitnessDiff = newDiff - oldDiff;

			if(newDiff<=oldDiff)
			{
				newBest = true;
				for(int i = positionToRemove + 1 ; i < polygons.size(); i++)
				{
					polygons[i]->positionZ--;
				}


				Neighbours emptyNeighbours;
				updateNeighboursAfterSizeChange(emptyNeighbours, poly);

				delete polygons[positionToRemove];
				polygons.erase(polygons.begin() + positionToRemove);


			}else
			{
				
			}

			mutated = true;
			return;
		}

	}

	struct PolyComparator {
		bool operator() (const CPolygon* lhs, const CPolygon* rhs) const
		{return lhs->positionZ<rhs->positionZ;}
	} polyComparator;



	void movePolygon(Polygons& polygons)
	{
		if(polygons.size() > 1)
		{
			int polyToMove = random(polygons.size() - 1);

			CPolygon* poly = polygons[polyToMove];

			int oldZ = polyToMove;
			int newZ = random(polygons.size() - 1);

			generated->draw_rectangle(poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY, black, 1);
			drawNeighboursAndMe(poly->neighbours, poly, generated);
			double oldDiff = comparator->compareFragment(original, generated, poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY);

			poly->positionZ = newZ;

			generated->draw_rectangle(poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY, black, 1);
			drawNeighboursAndMe(poly->neighbours, poly, generated);
			double newDiff = comparator->compareFragment(original, generated, poly->leftUpX, poly->leftUpY, poly->rightBottomX, poly->rightBottomY);

			fitnessDiff = newDiff - oldDiff;

			if(newDiff < oldDiff)
			{
				//std::cout<<"GOOD hanging z: "<<oldZ<<" "<<newZ<<std::endl;

				if(newZ < oldZ)
				{
					polygons.erase(polygons.begin() + oldZ);
					polygons.insert(polygons.begin() + newZ, poly);
				}else
				{
					if(newZ > oldZ)
					{
						polygons.insert(polygons.begin() + newZ, poly);
						polygons.erase(polygons.begin() + oldZ);
					}else
					{
						std::cout<<"WTF?"<<std::endl;
					}		
				}
				
				

				for(int i = 0; i < polygons.size(); i++)
				{
					polygons[i]->positionZ = i;
				}

				for(int i=0; i < poly->neighbours.size(); i++)
				{
					std::sort(poly->neighbours[i]->neighbours.begin(), poly->neighbours[i]->neighbours.end(), polyComparator);
				}

			}else
			{
			//	std::cout<<"BAD changing z: "<<oldZ<<" "<<newZ<<std::endl;
				poly->positionZ = oldZ;
			}

			mutated = true;
			return;

		}
	}
	
	bool newBest;
	bool mutated;
	double fitnessDiff;
	TriangleBuilder& builder;
	Image* original;
	Image* generated;
	ImageComparator* comparator;
	unsigned char black[3];
};

